chore(ci): migrate npm publish to trusted publishing via OIDC#1609
Merged
Conversation
The reactotron CI publish pipeline has been broken since npm revoked classic tokens on 2025-12-09 and the prior fix-attempt (PR #1602) left the renamed CircleCI context with an empty NPM_TOKEN. Rather than regenerate a granular token (capped at 90 days) and rotate forever, move publishing to npm Trusted Publishing via CircleCI OIDC. Changes: - Bump Yarn 4.1.1 → 4.14.1 (yarn 4.14 added CircleCI OIDC support). - .circleci/config.yml release_package job now mints a short-lived OIDC id-token via `circleci run oidc get --claims '{"aud":"npm:..."}'` and exports it as NPM_ID_TOKEN. - scripts/release.artifacts.mjs accepts either NPM_TOKEN or NPM_ID_TOKEN, then performs the npm OIDC token exchange directly (POST /-/npm/v1/oidc/token/exchange/package/<name>) and surfaces the result as NPM_TOKEN. The exchange is done in the script rather than by yarn because yarn 4.14.1's `yarn npm publish` skips the OIDC code path on CircleCI even though its `getOidcToken` helper supports it (gating bug — fix submitted upstream as yarnpkg/berry#7122). - .yarnrc.yml retains `npmAuthToken: "${NPM_TOKEN-}"` as a cutover soft-fallback (empty string is falsy in yarn's auth chain, so it doesn't block OIDC). - docs/contributing/releasing.md gains an OIDC section covering the CI flow, how to add a trusted publisher when shipping a new package, and the upstream yarn bug. Once yarnpkg/berry#7122 ships and we bump yarn, the OIDC exchange block in release.artifacts.mjs can be deleted and `yarn npm publish` will consume NPM_ID_TOKEN directly. Before merging this PR, the 11 published packages need a CircleCI trusted publisher configured on npmjs.com (org/project/context IDs from the CircleCI project settings). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous wording called the unrelated reactotron-mcp package "squatted", which mischaracterizes it. It's steve228uk's own project; just unrelated to Infinite Red. Reword to a neutral attribution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
joshuayoes
added a commit
that referenced
this pull request
May 4, 2026
## Please verify the following: - [x] `yarn build-and-test:local` passes - [ ] I have added tests for any new features, if relevant - [x] `README.md` (or relevant documentation) has been updated with your changes ## Describe your PR Bumps the `infinitered/publish-docs` orb from `@0.4` (resolves to v0.4.13) to `@0.5` (resolves to v0.5.1). This unbreaks `publish-docs/publish_docs` on master pushes when the merge commit body contains markdown / multi-line content. ### Why The orb's v0.4.13 wrote unescaped multi-line shell content to `$BASH_ENV` via `echo "export VAR=\"$VAL\""`. When the merge commit's body had colons (e.g. inline JSON snippets) or newlines, bash interpreted each line as a command after sourcing `$BASH_ENV` at the start of the next step. Symptom on the master push for #1609: ``` /tmp/.bash_env-...-build: line 39: README.md: command not found /tmp/.bash_env-...-build: line 39: NPM_TOKEN: command not found ... [40+ more] Error: Not a GitHub URL. Exited with code exit status 1 ``` ### Changes - `.circleci/config.yml` — bump `publish-docs: infinitered/publish-docs@0.4` → `@0.5`. ### Notes - Fix is upstream in [infinitered/orb-publish-docs#40](infinitered/orb-publish-docs#40), released as `v0.5.1` today. Replaces the unsafe `echo "export VAR=\"$VAL\""` patterns with `printf 'export VAR=%q\n' "$VAL"` in 5 internal scripts. - The `0.4 → 0.5` diff is internal only — no job, command, or parameter signatures change. All existing usage (`publish-docs/build_docs` and `publish-docs/publish_docs` with `ir_docs_config` params) remains compatible. - Verified `npx nx affected --target=version --base=origin/master --head=HEAD` returns 0 projects, so this merge will not cascade-trigger any unintended package releases. - Using `@0.5` (auto-resolves to latest 0.5.x) rather than pinned `@0.5.1` so future patch fixes apply automatically. Matches the prior `@0.4` style. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please verify the following:
yarn build-and-test:localpassesREADME.md(or relevant documentation) has been updated with your changesDescribe your PR
Migrates the reactotron CI publish pipeline from classic
NPM_TOKENauth to npm Trusted Publishing via CircleCI OIDC. npm GA'd CircleCI support on 2026-04-06; this PR wires us up.The pipeline has been broken since npm revoked classic tokens on 2025-12-09 and #1602 left the renamed
reactotron-npm-contextwith an emptyNPM_TOKEN. Rather than mint a granular replacement (capped at 90 days) and rotate forever, OIDC eliminates the human-managed token entirely.Changes
.circleci/config.yml(release_packagejob) — replaces thenpm whoami+~/.npmrctoken write with a "Mint npm OIDC token" step usingcircleci run oidc get --claims '{"aud":"npm:registry.npmjs.org"}'.scripts/release.artifacts.mjs— accepts eitherNPM_TOKENorNPM_ID_TOKEN, and performs the npm OIDC token exchange directly (POST /-/npm/v1/oidc/token/exchange/package/<ident>). This in-script exchange is a workaround for yarnpkg/berry#7122: Yarn 4.14.1'sgetOidcTokenhelper handles CircleCI, but theallowOidcgate inpublish.tsonly flips on forGITHUB_ACTIONS/GITLAB_CI. Once 7122 lands and we bump Yarn, the script-level exchange block can be deleted.4.1.1 → 4.14.1(4.14 brought the CircleCI OIDC support that 7122 finishes wiring up)..yarnrc.yml— keepsnpmAuthToken: "${NPM_TOKEN-}"as a soft fallback during cutover. Empty string is falsy in Yarn's auth chain, so it's a no-op when OIDC is in play. Removed in a follow-up PR after first prod publish.docs/contributing/releasing.md— new "OIDC publish flow" section covering the CI flow, how to add a trusted publisher when shipping a new package, and the upstream Yarn workaround.Pilot validation
Pilot package:
eslint-plugin-reactotron@0.1.10-beta.0. Pushed tag from branchtest/oidc-pilot-eslint, published under thebetadist-tag (semver prerelease — invisible to^/~/*ranges). Pipeline succeeded; package published.The npm registry metadata for the pilot version records:
This is the smoking-gun proof the publish ran via the trusted publisher path, not legacy token auth.
_npmVersion: nulland_nodeVersion: null(set when the publisher is OIDC) corroborate.Pre-merge prerequisites
Out of scope:
reactotron-app(Electron app, GitHub release artifacts only),reactotron-mcp("private": true).Follow-up PRs
After first successful production OIDC publish:
npmAuthToken: "${NPM_TOKEN-}"from.yarnrc.yml.release.artifacts.mjsto require onlyNPM_ID_TOKEN.NPM_TOKENfrom CircleCI context (currently absent; belt-and-suspenders).After yarnpkg/berry#7122 lands and we bump Yarn:
scripts/release.artifacts.mjs— Yarn handles the exchange directly viayarn npm publish.Notes
.circleci/,.yarnrc.yml, rootpackage.json,yarn.lock,scripts/release.artifacts.mjs,docs/,.yarn/releases/). Nolib/*orapps/*workspaces affected → 0 release tags created on merge → no auto-publish triggered. First real OIDC publish happens on the next legitimate code change inside alib/*workspace.🤖 Generated with Claude Code